//@author A0105536L
package Logic;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import Object.DSException;
import Object.DSQuery;
import Task.DSTask;
import Task.DSTaskList;

/******************************Main Logic part**********************************/
/**
 * 
 * class DSLogic. This is a main class that links other
 * components in the Logic part.
 * 
 */
public class DSLogic {

	public enum COMMAND_TYPE {
		COMMAND_BACKGROUND, COMMAND_ANIMATION, COMMAND_EXCEPTION, COMMAND_POSTPONE, COMMAND_UNDO, COMMAND_ADD, COMMAND_DISPLAY, COMMAND_DELETE, COMMAND_CLEAR, COMMAND_SEARCH, COMMAND_INVALID, COMMAND_UPDATE, COMMAND_DONE, COMMAND_UNDONE, COMMAND_OVERDUE, COMMAND_EXIT, COMMAND_EXPAND
	};

	private DSTaskList _taskList;
	private DSCommandExecuter _commandExecuter;
	private int expandStatus = -2;
	private boolean animationStatus;
	private DSTask _lastChangedTask;
	private String exceptionString;
	private int backgroundNum;

	/**
	 * Constructor of this class
	 */
	public DSLogic() {

		try {
			this._commandExecuter = new DSCommandExecuter();
		} catch (DSException e) {
			e.printStackTrace();
		}
		try {
			this._taskList = _commandExecuter.getTaskList();
		} catch (DSException e) {
			e.printStackTrace();
		}
		try {
			backgroundNum = this._commandExecuter.getBackgroundNum();
		} catch (DSException e1) {
			
			e1.printStackTrace();
		}
		try {
			animationStatus = this._commandExecuter.isAnimated();
		} catch (DSException e) {
			
			e.printStackTrace();
		}
		showAllTasks();
		_taskList.sort(DSTaskList.SORT_DEFAULT);
	}

	/**
	 * Execute user's command
	 * 
	 * @param Command
	 *            The string that user types in
	 * @return Command type of the correct command
	 */
	public COMMAND_TYPE executeCommand(String Command) {

		String _firstCommand = getFirstWord(Command);
		String _secondHalfCommand = removeFirstWord(Command);

		switch (DSCommandParser.parseCommand(_firstCommand, _secondHalfCommand)) {

		case COMMAND_ADD:

			try {
				DSTask _newTask;
				_newTask = DSTaskFactory.createNewTask(_secondHalfCommand);

				if (_commandExecuter.add(_newTask)) {
					_taskList.add(_newTask);
					_taskList.sort(DSTaskList.SORT_DEFAULT);
					_lastChangedTask = _newTask;
					return COMMAND_TYPE.COMMAND_ADD;
				}

			} catch (DSException e) {
				exceptionString = "Add operation encounters error.";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_DELETE:

			try {
				int delete_taskIndex = Integer.parseInt(_secondHalfCommand);
				if (delete_taskIndex > _taskList.size()) {
					break;
				}
				if (_commandExecuter.delete(_taskList.get(delete_taskIndex - 1)
						.getTaskId())) {
					_lastChangedTask = _taskList.get(delete_taskIndex - 1);
					showToUser("deleted "
							+ _taskList.remove(delete_taskIndex - 1)
									.getDescription());
					_taskList.sort(DSTaskList.SORT_DEFAULT);
					return COMMAND_TYPE.COMMAND_DELETE;
				}
			} catch (DSException e) {
				exceptionString = "Delete operation encounters error";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_EXIT:
			_commandExecuter.exit();

		case COMMAND_UPDATE:
			int update_taskIndex = Integer
					.parseInt(getFirstWord(_secondHalfCommand));
			if (update_taskIndex > _taskList.size()) {
				break;
			}

			try {

				DSTask oldTask = _taskList.get(update_taskIndex - 1);
				DSTask updatedTask = DSTaskFactory.updateTask(
						removeFirstWord(_secondHalfCommand), oldTask);
				if (_commandExecuter.update(updatedTask)) {
					_taskList.set(update_taskIndex - 1, updatedTask);
					_taskList.sort(DSTaskList.SORT_DEFAULT);
					_lastChangedTask = updatedTask;
				}
				return COMMAND_TYPE.COMMAND_UPDATE;
			} catch (DSException e) {
				exceptionString = "Update operation encounters error";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_ANIMATION:

			
			return COMMAND_TYPE.COMMAND_ANIMATION;

		case COMMAND_DONE:

			try {
				int done_taskIndex = Integer.parseInt(_secondHalfCommand);
				if (done_taskIndex > _taskList.size()) {
					break;
				}
				_taskList.get(done_taskIndex - 1).setIsDone(true);
				_commandExecuter.update(_taskList.get(done_taskIndex - 1));
				_taskList.sort(DSTaskList.SORT_DEFAULT);
				return COMMAND_TYPE.COMMAND_DONE;

			} catch (DSException e) {
				exceptionString = "Done operation encounters error";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_EXPAND:

			if (isInteger(_firstCommand)) {
				if (Integer.parseInt(_firstCommand) > _taskList.size()) {
					break;
				}
				expandStatus = Integer.parseInt(_firstCommand);
				return COMMAND_TYPE.COMMAND_EXPAND;
			} else if (_firstCommand.equals("expand")
					&& _secondHalfCommand.length() == 0) {
				expandStatus = -1;
				return COMMAND_TYPE.COMMAND_EXPAND;
			} else if (_firstCommand.equals("expand")
					&& _secondHalfCommand.length() != 0) {
				if (Integer.parseInt(_secondHalfCommand) > _taskList.size()) {
					break;
				}
				expandStatus = Integer.parseInt(_secondHalfCommand);
				return COMMAND_TYPE.COMMAND_EXPAND;
			} else if (_firstCommand.equals("hide")
					&& _secondHalfCommand.length() == 0) {
				expandStatus = -2;
				return COMMAND_TYPE.COMMAND_EXPAND;
			} else if (_firstCommand.equals("hide")
					&& _secondHalfCommand.length() != 0) {
				if (Integer.parseInt(_secondHalfCommand) > _taskList.size()) {
					break;
				}
				expandStatus = Integer.parseInt(_secondHalfCommand);
				return COMMAND_TYPE.COMMAND_EXPAND;
			} else {
				return COMMAND_TYPE.COMMAND_EXPAND;
			}

		case COMMAND_POSTPONE:
			int postpone_taskIndex = Integer
					.parseInt(getFirstWord(_secondHalfCommand));
			if (postpone_taskIndex > _taskList.size()) {
				break;
			}

			DSTask postTask = _taskList.get(postpone_taskIndex - 1);
			if (_secondHalfCommand.length() <= 2) {
				try {
					if (isFloating(postTask)) {
						// if the task is a floating task
						return COMMAND_TYPE.COMMAND_INVALID;
					} else if (onlyEnd(postTask)) {
						// if the task only has end date
						postTask.postTaskEndDate(postDate(1,
								postTask.getTaskEndDate()));
						_commandExecuter.update(postTask);
						_taskList.sort(DSTaskList.SORT_DEFAULT);
						return COMMAND_TYPE.COMMAND_POSTPONE;
					} else if (bothStartandEnd(postTask)) {
						postTask.postTaskEndDate(postDate(1,
								postTask.getTaskEndDate()));
						postTask.postTaskStartDate(postDate(1,
								postTask.getTaskStartDate()));
						_commandExecuter.update(postTask);
						_taskList.sort(DSTaskList.SORT_DEFAULT);
						return COMMAND_TYPE.COMMAND_POSTPONE;
					} else {
						return COMMAND_TYPE.COMMAND_INVALID;
					}
				} catch (ParseException e) {
					e.printStackTrace();
				} catch (DSException e) {
					exceptionString = "Postpone operation encounters error";
					return COMMAND_TYPE.COMMAND_EXCEPTION;
				}
			} else {
				String days = removeFirstWord(_secondHalfCommand);

				try {
					if (isFloating(postTask)) {
						// if the task is a floating task
						return COMMAND_TYPE.COMMAND_INVALID;
					} else if (onlyEnd(postTask)) {
						// if the task only has end date

						if (postponeEndDate(days, postTask))
							;
						_commandExecuter.update(postTask);
						_taskList.sort(DSTaskList.SORT_DEFAULT);
						return COMMAND_TYPE.COMMAND_POSTPONE;
					} else if (bothStartandEnd(postTask)) {
						if (postponeStartandEnd(days, postTask))
							;

						_commandExecuter.update(postTask);
						_taskList.sort(DSTaskList.SORT_DEFAULT);
					} else {
						return COMMAND_TYPE.COMMAND_INVALID;
					}
				} catch (ParseException e) {
					e.printStackTrace();
				} catch (DSException e) {
					exceptionString = "Postpone operation encounters error";
					return COMMAND_TYPE.COMMAND_EXCEPTION;
				}
			}
		case COMMAND_DISPLAY:
			try {
				_taskList = _commandExecuter.displayall();
				_taskList.sort(DSTaskList.SORT_DEFAULT);

				return COMMAND_TYPE.COMMAND_DISPLAY;
			} catch (DSException e) {

			}

		case COMMAND_OVERDUE:
			DSQuery query = DSQueryFactory.overdueQuery();
			try {
				_taskList = _commandExecuter.search(query);
				_taskList.sort(DSTaskList.SORT_DEFAULT);
				return COMMAND_TYPE.COMMAND_OVERDUE;
			} catch (DSException e) {
				exceptionString = "Overdue operation encounters error";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_UNDONE:
			int undone_taskIndex = Integer.parseInt(_secondHalfCommand);
			if (undone_taskIndex > _taskList.size()) {
				break;
			}
			_taskList.get(undone_taskIndex - 1).setIsDone(false);
			try {
				_commandExecuter.update(_taskList.get(undone_taskIndex - 1));
				_taskList.sort(DSTaskList.SORT_DEFAULT);
				return COMMAND_TYPE.COMMAND_UNDONE;
			} catch (DSException e) {
				exceptionString = "Undone operation encounters error";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_SEARCH:

			try {
				DSQuery searchQuery = DSQueryFactory
						.createQuery(_secondHalfCommand);
				_taskList = _commandExecuter.search(searchQuery);
				_taskList.sort(DSTaskList.SORT_DEFAULT);
				return COMMAND_TYPE.COMMAND_SEARCH;
			} catch (DSException e) {
				exceptionString = "Search operation encounters error";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_UNDO:
			try {
				_taskList = _commandExecuter.undo();
				_taskList.sort(DSTaskList.SORT_DEFAULT);
				return COMMAND_TYPE.COMMAND_UNDO;
			} catch (DSException e) {
				exceptionString = "Undo operation encounters error";
				return COMMAND_TYPE.COMMAND_EXCEPTION;
			}

		case COMMAND_BACKGROUND:
			
			return COMMAND_TYPE.COMMAND_BACKGROUND;

		case COMMAND_INVALID:

			return COMMAND_TYPE.COMMAND_INVALID;
		}

		return COMMAND_TYPE.COMMAND_INVALID;
	}

	/**
	 * This method is to postpone a date
	 * 
	 * @param days
	 *            Integer to postpone days
	 * @param date
	 *            Date object. A specific date to be postponed
	 * @return New date
	 */
	private static Date postDate(int days, Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.DATE, days);
		return calendar.getTime();
	}

	private static boolean postponeEndDate(String days, DSTask postTask) {
		if (days.equals("one week") || days.equals("1 week")) {
			postTask.postTaskEndDate(postDate(7, postTask.getTaskEndDate()));
			return true;
		} else if (days.equals("two days") || days.equals("2 days")) {
			postTask.postTaskEndDate(postDate(2, postTask.getTaskEndDate()));
			return true;
		} else if (days.equals("3 days") || days.equals("three days")) {
			postTask.postTaskEndDate(postDate(3, postTask.getTaskEndDate()));
			return true;
		} else if (days.equals("4 days") || days.equals("four days")) {
			postTask.postTaskEndDate(postDate(4, postTask.getTaskEndDate()));
			return true;
		} else if (days.equals("5 days") || days.equals("five days")) {
			postTask.postTaskEndDate(postDate(5, postTask.getTaskEndDate()));
			return true;
		} else if (days.equals("6 days") || days.equals("six days")) {
			postTask.postTaskEndDate(postDate(6, postTask.getTaskEndDate()));
			return true;
		} else if (days.equals("one month") || days.equals("1 month")) {
			postTask.postTaskEndDate(postDate(30, postTask.getTaskEndDate()));
			return true;
		} else {
			return false;
		}
	}

	private static boolean postponeStartandEnd(String days, DSTask postTask) {
		if (days.equals("one week") || days.equals("1 week")) {
			postTask.postTaskEndDate(postDate(7, postTask.getTaskEndDate()));
			postTask.postTaskStartDate(postDate(7, postTask.getTaskStartDate()));
			return true;
		} else if (days.equals("two days") || days.equals("2 days")) {
			postTask.postTaskEndDate(postDate(2, postTask.getTaskEndDate()));
			postTask.postTaskStartDate(postDate(2, postTask.getTaskStartDate()));
			return true;
		} else if (days.equals("3 days") || days.equals("three days")) {
			postTask.postTaskEndDate(postDate(3, postTask.getTaskEndDate()));
			postTask.postTaskStartDate(postDate(3, postTask.getTaskStartDate()));
			return true;
		} else if (days.equals("4 days") || days.equals("four days")) {
			postTask.postTaskEndDate(postDate(4, postTask.getTaskEndDate()));
			postTask.postTaskStartDate(postDate(4, postTask.getTaskStartDate()));
			return true;
		} else if (days.equals("5 days") || days.equals("five days")) {
			postTask.postTaskEndDate(postDate(5, postTask.getTaskEndDate()));
			postTask.postTaskStartDate(postDate(5, postTask.getTaskStartDate()));
			return true;
		} else if (days.equals("6 days") || days.equals("six days")) {
			postTask.postTaskEndDate(postDate(6, postTask.getTaskEndDate()));
			postTask.postTaskStartDate(postDate(6, postTask.getTaskStartDate()));
			return true;
		} else if (days.equals("one month") || days.equals("1 month")) {
			postTask.postTaskEndDate(postDate(30, postTask.getTaskEndDate()));
			postTask.postTaskStartDate(postDate(30, postTask.getTaskStartDate()));
			return true;
		} else {
			return false;
		}
	}

	/**
	 * This method is to check given task is a floating task or not
	 * 
	 * @param task
	 *            Task to check
	 * @return True if the task is a floating task
	 * @throws ParseException
	 */
	private static boolean isFloating(DSTask task) throws ParseException {

		Date check;
		DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
		String checkDate = "01-01-1111";
		check = formatter.parse(checkDate);
		return task.getTaskStartDate().equals(check)
				&& task.getTaskEndDate().equals(check);
	}

	/**
	 * This method is to check given task only has end date.
	 * 
	 * @param task
	 *            Task to check
	 * @return True if the task only has end date
	 * @throws ParseException
	 */
	private static boolean onlyEnd(DSTask task) throws ParseException {

		Date check;
		DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
		String checkDate = "01-01-1111";
		check = formatter.parse(checkDate);
		return task.getTaskStartDate().equals(check)
				&& !task.getTaskEndDate().equals(check);
	}

	/**
	 * This method is to check given task only has start date and end date
	 * 
	 * @param task
	 *            Task to check
	 * @return True if the task only has start date and end date
	 * @throws ParseException
	 */
	private static boolean bothStartandEnd(DSTask task) throws ParseException {

		Date check;
		DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
		String checkDate = "01-01-1111";
		check = formatter.parse(checkDate);
		return !task.getTaskStartDate().equals(check)
				&& !task.getTaskEndDate().equals(check);
	}

	/**
	 * This method is to remove the first word of the given string
	 * 
	 * @param userCommand
	 *            String to get rid of the first word
	 * @return A string without the first word of the original string
	 */
	private static String removeFirstWord(String userCommand) {

		return userCommand.replaceFirst(getFirstWord(userCommand), "").trim();

	}

	/**
	 * This method is to get the first word of the given string
	 * 
	 * @param userCommand
	 *            String to get the first word
	 * @return String that is the first word of the input string
	 */
	private static String getFirstWord(String userCommand) {

		String commandTypeString = userCommand.trim().split("\\s+")[0];
		return commandTypeString;

	}

	/**
	 * This method is to get expand status
	 * 
	 * @return Integer of expand status
	 */
	public int getExpandStatus() {
		return expandStatus;
	}
/**
 * This method is to get animation status.
 * @return
 * 		Return the animation status
 */
	public boolean getAnimation(){
		return this.animationStatus;
	}
	/**
	 * This method is to get background number
	 * @return
	 * 		Return background number
	 */
	public int getBackgroundNum(){
		return this.backgroundNum;
	}

	/**
	 * This method is to set animation status
	 * 
	 * @param status
	 *            Boolean value of the status
	 * @throws DSException 
	 */
	public void setAnimation(boolean status) throws DSException {
		 this._commandExecuter.setAnimation(status);
	}
	/**
	 * This method is to set background number
	 * @param bgn
	 * 			current background number
	 * @throws DSException
	 */
	public void setBackgroundNum(int bgn) throws DSException{
		this._commandExecuter.setBackgroundNum(bgn);
	}
	
	/**
	 * This method is to get exception string
	 * 
	 * @return String that contains exception message
	 */
	public String getExceptionString() {

		return this.exceptionString;
	}

	/**
	 * This method is to check if there is only integer in a given string
	 * 
	 * @param integerString
	 *            String to be checked
	 * @return True if the string contains only integer. e.g "123"-->true;
	 *         "123abc"-->false
	 */
	private static boolean isInteger(String integerString) {

		try {
			Integer.parseInt(integerString);
			return true;
		} catch (NumberFormatException nfe) {
			return false;
		}
	}

	private static void showToUser(String message) {
		if (!message.equals("")) {
			System.out.println(message);
		}
	}

	private void showAllTasks() {
		System.out.println("Show all tasks:");

		for (int i = 0; i < _taskList.size(); i++)
			System.out.println(i + 1 + " " + _taskList.get(i).getDescription());

		System.out.println("");
	}

	public DSCommandExecuter getExecuter(){
		return this._commandExecuter;
	}
	
	public DSTaskList getTaskList() {
		return _taskList;
	}

	public Object[] getTaskListAsArray() {
		return _taskList.getArray();
	}

	public DSTask getLastChangedTask() {
		return _lastChangedTask;
	}

}